﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Krile.Kernel.Data;

namespace Krile.Query
{
    /// <summary>
    /// クエリサブシステム (v2)
    /// </summary>
    public static class QueryEvaluator
    {
        public static SearchDataCluster ToFilter(string query)
        {
            var tokenizer = Tokenize("(" + query + ")");
            var enume = tokenizer.GetEnumerator();
            if (!enume.MoveNext())
                throw new ArgumentException("L0 failed");
            return SimplifyCluster(new SearchDataCluster(new SearchDataBase[] { Layer0(enume) }));
        }

        /// <summary>
        /// クエリをトークン化します。
        /// </summary>
        /// <param name="source">ソース文字列</param>
        /// <returns>トークン文字列</returns>
        private static IEnumerable<TokenData> Tokenize(string source)
        {
            // 入力文字の検証

            // source の除染
            source = source.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", "");

            // ()の対応を調べる
            int depth = 0;
            for (int i = 0; i < source.Length; i++)
            {
                if (source[i] == '(')
                {
                    depth++;
                }
                else if (source[i] == ')')
                {
                    depth--;
                }
                else if (source[i] == '\"')
                {
                    for (int j = i + 1; j < source.Length; j++)
                    {
                        if (source[j] == '\"')
                            break;
                        else if (source[j] == '\\')
                            j++;
                    }
                }
                if (depth < 0)
                {
                    throw new ArgumentException("括弧が対応していません。[Note:閉じ括弧が多すぎる可能性があります]");
                }
            }
            if (depth != 0)
            {
                throw new ArgumentException("括弧が対応していません。[Note:括弧が閉じられていない可能性があります。個数:" + depth + "]");
            }

            // 読み込み対象文字位置
            while (source.Length > 0)
            {
                switch (source[0])
                {
                    // spacer
                    case ' ':
                    case '\t':
                        // 読み飛ばす
                        source = source.Substring(1);
                        break;

                    // Opener / Closer
                    case '(':
                        // opener
                        source = source.Substring(1);
                        yield return new TokenData(TokenData.TokenKind.Opener, "(");
                        break;

                    case ')':
                        // closer
                        source = source.Substring(1);
                        yield return new TokenData(TokenData.TokenKind.Closer, ")");
                        break;

                    case '\"':
                        // 文字列
                        int i = 1;
                        for (; i < source.Length; i++)
                        {
                            if (source[i] == '\"')
                                break;
                            else if (source[i] == '\\')
                                i++;
                        }
                        if (i == source.Length)
                        {
                            throw new ArgumentException("文字列が閉じられていません。残りトークン: " + source);
                        }
                        else
                        {
                            yield return new TokenData(TokenData.TokenKind.String, source.Substring(1, i - 1));
                        }
                        source = source.Substring(i + 1);
                        break;

                    case '&':
                        // concatenator?
                        if (source.Length == 1 || source[1] != '&')
                        {
                            throw new ArgumentException("不正なトークンが含まれています: " + source);
                        }
                        yield return new TokenData(TokenData.TokenKind.Concatenator, "&&");
                        source = source.Substring(2);
                        break;

                    case '|':
                        // concatenator?
                        if (source.Length == 1 || source[1] != '|')
                        {
                            throw new ArgumentException("不正なトークンが含まれています: " + source);
                        }
                        yield return new TokenData(TokenData.TokenKind.Concatenator, "||");
                        source = source.Substring(2);
                        break;

                    case '!':
                        yield return new TokenData(TokenData.TokenKind.Negator, "!");
                        source = source.Substring(1);
                        break;

                    case ',':
                        yield return new TokenData(TokenData.TokenKind.Delimitor, ",");
                        source = source.Substring(1);
                        break;

                    case '.':
                        yield return new TokenData(TokenData.TokenKind.Period, ".");
                        source = source.Substring(1);
                        break;

                    default:
                        // word or value
                        int endpoint = source.IndexOfAny(new[] { '.', ',', '\"', '(', ')' });
                        if (endpoint == -1)
                        {
                            throw new ArgumentException("不正なトークンが含まれています: " + source);
                        }
                        else
                        {
                            yield return new TokenData(TokenData.TokenKind.WordOrValue, source.Substring(0, endpoint));
                            source = source.Substring(endpoint);
                        }
                        break;

                }
            }
        }

        private class TokenData
        {
            public string Value { get; set; }

            public enum TokenKind
            {
                Opener,
                Closer,
                String,
                Concatenator,
                Negator,
                Delimitor,
                WordOrValue,
                Period
            }

            public TokenKind Kind { get; set; }

            public TokenData(TokenKind kind, string value)
            {
                this.Kind = kind;
                this.Value = value;
            }

            public override string ToString()
            {
                return "[" + Kind.ToString() + "]" + Value;
            }
        }

        private static void AssertMoveNext(this IEnumerator<TokenData> token, string message)
        {
            if (!token.MoveNext())
                throw new ArgumentException(message);
        }

        private static SearchDataBase Layer0(IEnumerator<TokenData> token)
        {
            // ネゲート判定
            if (token.Current.Kind == TokenData.TokenKind.Negator)
            {
                if (!token.MoveNext())
                    throw new ArgumentException("構文解析エラー: 否定演算子の次の演算子を取得できません。");
                var ret = Layer1(token);
                ret.Exclude = true;
                return ret;
            }
            else
            {
                return Layer1(token);
            }
        }

        private static SearchDataBase Layer1(IEnumerator<TokenData> token)
        {
            // (0)
            // (0 && 0 && ...)
            // (0 || 0 || ...)
            // の変換を担当
            List<SearchDataBase> bases = new List<SearchDataBase>();
            bool? concatenateAnd = null;
            bool openAssert = false;
            while (true)
            {
                switch (token.Current.Kind)
                {
                    case TokenData.TokenKind.Opener:
                        // 開始括弧
                        openAssert = true;
                        token.AssertMoveNext("L1 failed");
                        // 差し戻し
                        bases.Add(Layer0(token));
                        break;
                    case TokenData.TokenKind.Concatenator:
                        // 接続演算子
                        if (!openAssert)
                        {
                            throw new ArgumentException("構文解析エラー: 接続演算子だけがあります。token:" + token.Current.ToString());
                        }
                        bool cand = token.Current.Value == "&&";
                        if (concatenateAnd != null && cand != concatenateAnd)
                        {
                            throw new ArgumentException("構文解析エラー: 同じ階層の()の中で、&&と||を混ぜて書くことはできません。");
                        }
                        concatenateAnd = cand;
                        token.AssertMoveNext("L1 failed(2)");
                        // 差し戻し
                        bases.Add(Layer0(token));
                        break;
                    case TokenData.TokenKind.Closer:
                        // 終了括弧
                        if (!openAssert)
                        {
                            throw new ArgumentException("構文解析エラー: 閉じ括弧だけがあります。token:" + token.Current.ToString());
                        }
                        token.MoveNext();
                        var filter = new SearchDataCluster();
                        filter.SearchDatas = bases.ToArray();
                        if (concatenateAnd != null)
                            filter.UseOrMatch = !concatenateAnd.Value;
                        return filter;
                    default:
                        // 括弧は無いようだ
                        return Layer2(token);
                }
            }
        }

        private static SearchDataBase Layer2(IEnumerator<TokenData> token)
        {
            // 単純なメソッド表現だけになっているはず
            if (token.Current.Kind != TokenData.TokenKind.WordOrValue)
                throw new ArgumentException("構文解析エラー: クラス名ではありません。token:" + token.Current.ToString());
            var cname = token.Current.Value;
            token.AssertMoveNext("構文解析エラー: クラス名までしかありません。");

            if (token.Current.Kind != TokenData.TokenKind.Period)
                throw new ArgumentException("構文解析エラー: クラス名とメソッド名のデリミタはピリオドである必要があります。token:" + token.Current.ToString());
            token.AssertMoveNext("構文解析エラー: ピリオドまでしかありません。");

            if (token.Current.Kind != TokenData.TokenKind.WordOrValue)
                throw new ArgumentException("構文解析エラー: メソッド名ではありません。token:" + token.Current.ToString());
            var mname = token.Current.Value;
            token.AssertMoveNext("構文解析エラー: メソッド名までしかありません。");
            foreach (var rmf in Core.StatusManager.GetAllSearchData())
            {
                if (rmf.Class == cname && rmf.Methods.Contains(mname))
                {
                    return Layer3(token, rmf, mname);
                }
            }
            throw new ArgumentException("構文解析エラー: 該当するクラス名/メソッド名を持つフィルタは存在しませんでした。クラス: " + cname + " メソッド: " + mname);
        }

        private static SearchDataBase Layer3(IEnumerator<TokenData> token, SearchData target, string method = null)
        {
            // コピーの生成
            var impl = Activator.CreateInstance(target.GetType()) as SearchData;
            if (token.Current.Kind != TokenData.TokenKind.Opener)
                throw new ArgumentException("構文解析エラー: 引数は()で括らなければなりません。");
            token.AssertMoveNext("L3 failed(before)");
            List<object> args = new List<object>();
            do
            {
                switch (token.Current.Kind)
                {
                    case TokenData.TokenKind.String:
                    case TokenData.TokenKind.WordOrValue:
                        if(token.Current.Kind == TokenData.TokenKind.String)
                        {
                            // string
                            args.Add(token.Current.Value);
                        }
                        else if (token.Current.Value.Equals("true", StringComparison.CurrentCultureIgnoreCase))
                        {
                            // boolean: true
                            args.Add(true);
                        }
                        else if (token.Current.Value.Equals("false", StringComparison.CurrentCultureIgnoreCase))
                        {
                            // boolean: false
                            args.Add(false);
                        }
                        else if (token.Current.Value.Except("0123456789+-*/^()".ToCharArray()).Count() > 0)
                        {
                            // long
                            args.Add(GetValue(token.Current.Value));
                        }
                        else
                        {
                            // unknown word
                            args.Add(new EnumValue(token.Current.Value));
                        }
                        token.AssertMoveNext("L3 failed(while)");
                        break;
                    case TokenData.TokenKind.Closer:
                        // closer
                        break;
                    default:
                        throw new ArgumentException("構文解析エラー: 無効なトークンです。: " + token.Current.ToString());
                }
                switch (token.Current.Kind)
                {
                    case TokenData.TokenKind.Closer:
                        // 引数終了
                        try
                        {
                            impl.AcceptArgument(method, args.ToArray());
                        }
                        catch (ArgumentException e)
                        {
                            // 引数ミス
                            var argtype = from s in args
                                          select (s is string ? "string" : (s is long ? "long" : (s is bool ? "bool" : (s is EnumValue ? "enum" : "unknown"))));
                            var arguments = String.Join(", ", argtype.ToArray());
                            throw new ArgumentException("引数が違います: " + impl.Class + "." + method + "(" + e.Message + ") ですが、(" + argtype.Count() + ":" + arguments + ")が指定されました。");
                        }
                        token.MoveNext(); // )の次を読む
                        return impl;
                    case TokenData.TokenKind.Delimitor:
                        // 引数継続
                        break;
                    default:
                        throw new ArgumentException("構文解析エラー: 無効なトークンです。: " + token.Current.ToString());
                }
            } while (token.MoveNext());
            throw new ArgumentException("L3 failed");
        }

        #region 式解析

        // 式表現から値を求めます。
        private static long GetValue(string expr)
        {
            // 空白文字の除去
            expr = expr.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", "");

            // + - * / ^ ( )
            return _Depth0(expr);
        }

        private static long _Depth0(string expr)
        {
            // ( )内を先に計算する
            int ob = expr.IndexOf('(');
            if (ob == -1)
            {
                // ( ) なし
                return _Depth1(expr);
            }
            else
            {
                // ( ) あり
                int cbp = ob + 1;
                int dp = 0;
                for (; cbp < expr.Length; cbp++)
                {
                    if (expr[cbp] == ')')
                    {
                        dp--;
                        if (dp == 0)
                            break;
                    }
                    else if (expr[cbp] == '(')
                    {
                        dp++;
                    }
                }
                if (cbp == expr.Length)
                    throw new ArgumentException("式を解析できません: 括弧が対応していません: " + expr);
                expr = expr.Substring(0, ob) + _Depth0(expr.Substring(ob + 1, cbp - ob)).ToString() + expr.Substring(cbp + 1);
                return _Depth0(expr);
            }
        }

        private static long _Depth1(string expr)
        {
            // + - を計算
            var pos = expr.IndexOfAny(new[] { '+', '-' });
            if (pos == -1)
            {
                return _Depth2(expr);
            }
            else
            {
                var oper = expr[pos];
                var left = expr.Substring(0, pos);
                var right = expr.Substring(pos + 1);
                if (left.Length == 0 || right.Length == 0)
                {
                    throw new ArgumentException("式を解析できません: 演算子を先頭や末尾に置くことはできません: " + expr);
                }
                switch (oper)
                {
                    case '+':
                        // add
                        return _Depth2(left) + _Depth1(right);
                    case '-':
                        // subtract
                        return _Depth2(left) - _Depth1(right);
                    default:
                        throw new ArgumentException("内部解析エラー: 不明な演算子です: " + expr);
                }
            }
        }

        private static long _Depth2(string expr)
        {
            var pos = expr.IndexOfAny(new[] { '*', '/' });
            if (pos == -1)
            {
                return _Depth3(expr);
            }
            else
            {
                var oper = expr[pos];
                var left = expr.Substring(0, pos);
                var right = expr.Substring(pos + 1);
                if (left.Length == 0 || right.Length == 0)
                {
                    throw new ArgumentException("式を解析できません: 演算子を先頭や末尾に置くことはできません: " + expr);
                }
                switch (oper)
                {
                    case '*':
                        // multiply
                        return _Depth3(left) * _Depth2(right);
                    case '/':
                        // divide
                        var rv = _Depth2(right);
                        if (rv == 0)
                            throw new ArgumentException("式を解析できません: ゼロで除算はできません: " + expr);
                        return _Depth3(left) / rv;
                    default:
                        throw new ArgumentException("内部解析エラー: 不明な演算子です: " + expr);
                }
            }
        }

        private static long _Depth3(string expr)
        {
            // ^ を計算
            var pos = expr.IndexOf('^');
            if (pos == -1)
            {
                return _GetValue(expr);
            }
            else
            {
                var left = expr.Substring(0, pos);
                var right = expr.Substring(pos + 1);
                if (left.Length == 0 || right.Length == 0)
                {
                    throw new ArgumentException("式を解析できません: 演算子を先頭や末尾に置くことはできません: " + expr);
                }
                return (long)Math.Pow(_GetValue(left), _Depth3(right));
            }
        }

        private static long _GetValue(string expr)
        {
            // 最終的な値を求める
            long val;
            if (long.TryParse(expr, out val))
            {
                return val;
            }
            else
            {
                throw new ArgumentException("式を解析できません: 数値に変換できませんでした: " + expr);
            }
        }

        #endregion

        public static string ToQuery(SearchDataCluster cluster)
        {
            return cluster.ToQuery();
        }

        /// <summary>
        /// クラスタを簡単化します。
        /// </summary>
        public static SearchDataCluster SimplifyCluster(SearchDataCluster cluster)
        {
            if (cluster.SearchDatas == null)
                return cluster;
            var simplified = SimplifyFilter(cluster);
            if (simplified is SearchDataCluster)
            {
                return (SearchDataCluster)simplified;
            }
            else
            {
                return new SearchDataCluster(new[] { simplified });
            }
        }

        private static SearchDataBase SimplifyFilter(SearchDataBase filter)
        {
            var cluster = filter as SearchDataCluster;
            if (cluster != null)
            {
                // クラスタの簡単化
                if (cluster.SearchDatas.Length == 0)
                {
                    // (ﾟдﾟ)
                    return null;
                }
                else if (cluster.SearchDatas.Length == 1)
                {
                    // このクラスタいらんよね
                    if (cluster.Exclude)
                    {
                        // 否定フラグを変更
                        cluster.SearchDatas[0].Exclude = !cluster.SearchDatas[0].Exclude;
                    }
                    // 階層を深くする
                    return SimplifyFilter(cluster.SearchDatas[0]);
                }
                else
                {
                    var newlist = new List<SearchDataBase>();
                    foreach (var f in GetClusterChild(cluster))
                    {
                        var nf = SimplifyFilter(f);
                        if (nf != null)
                            newlist.Add(nf);
                    }
                    cluster.SearchDatas = newlist.ToArray();
                    return cluster;
                }
            }
            else
            {
                // フィルタに対してできることは無いんじゃないでしょうか
                return filter;
            }
        }

        private static IEnumerable<SearchDataBase> GetClusterChild(SearchDataCluster cluster)
        {
            if (cluster.SearchDatas == null || cluster.SearchDatas.Length == 0)
            {
                yield break;
            }
            else
            {
                foreach (var sd in cluster.SearchDatas)
                {
                    var sdc = sd as SearchDataCluster;
                    if (sdc != null)
                    {
                        if (sdc.UseOrMatch == cluster.UseOrMatch)
                        {
                            // コンカテネータが一緒なので、まんま行けばいいんじゃね
                            foreach (var item in GetClusterChild(sdc))
                                yield return item;
                        }
                        else
                        {
                            yield return sd;
                        }
                    }
                    else
                    {
                        yield return sd;
                    }
                }
            }
        }
    }
}
